
package SSF.Net;

import SSF.Net.Util.*;

import java.util.*;
import java.io.*;

import SSF.OS.*;
import com.renesys.raceway.DML.*;
import com.renesys.raceway.SSF.*;

import SSF.Util.Random.*;
import cern.jet.random.*;

import SSF.OS.TCP.*;

import java.lang.Class; // temp ML

/**
  * Network Interface Card.
  *
  * <P>The NIC is a pseudo-protocol driver that resides at the border between
  * the world of the "quasi-operating system" of SSF.OS (where the protocols
  * interact via the push() of ProtocolMessages between ProtocolSessions), and
  * the world of a discrete event simulation (SSF API) which implements
  * external transmission of encapsulated messages over links (characterized by the
  * exchange of PacketEvents in positive elapsed simulation time).
  *
  * <P>From the point of view of the Host or Router protocol graph, NIC configures
  * and initializes a queue of outgoing ProtocolMessages (a class implementing SSF.Net.packetQueue),
  * and optionally configures and initializes a packet queue monitoring probe
  * (a class implementing SSF.Net.PacketQueueMonitor).
  *
  * <P>From the point of view of transmission of messages over links, a NIC maintains
  *  a pair of SSF channels to the world outside the protocol graph.
  * A PacketEvent that arrives on the IN channel is converted to a ProtocolMessage and
  * without delay pushed up to the protocol who opened the NIC (typically, IP) for
  * demultiplexing to the destination ProtocolSession.
  *
  * <P> A ProtocolMessage that is pushed down to the NIC from a protocol
  * above it is handed to a queue manager, which is an instance of a class implementing the
  * Java interface <TT>SSF.Net.packetQueue</TT>.
  * The queue manager will enqueue (or drop) the packet in an implementation-dependent manner;
  * and schedule the packet event transmission on behalf of the NIC.
  * The default packet queue manager is the droptail (FIFO) queue,
  * but the modeler may specify another class implementing SSF.Net.packetQueue in
  * the DML attribute <TT>interface.queue.use</TT>.
  *
  * <P>When a NIC is constructed and configured, it has an IP address
  * assigned, but is "down" (e.g., inactive). 
  * A NIC is brought "up" by an open() operation invoked by an instance 
  * of class <b>link</b>, receiving two arguments: a reference to the 
  * protocol configured over the NIC (usually, but not necessarily, IP),
  * and a reference to the link which is being brought up.<p>  
  *
  * In the simplified "quasi-operating system" of SSF.OS, there are 
  * only two sources of interrupts: Timer callbacks (see SSF.OS.Timer) and packet
  * arrivals at network interface cards.  These 'interrupts' initiate 
  * all processing within the protocol graph; the process of servicing 
  * an interrupt itself consumes zero simulation time.<p>
  */
public class NIC extends ProtocolSession {

  /** Index of this NIC --- must be unique within one host. */
  public int ID;

  /** 32-bit integer IP address of this NIC. */
  public int ipAddr;

  /** Number of significant bits in the network mask of this NIC (prefix lenth).
    * For example, a NIC in a class C network would have maskBits=24.
    */
  public int maskBits;

  /** Returns the complete classless address of this NIC,
    * equal to the IP address in a.b.c.d format, followed by a forward 
    * slash, followed by the number of significant bits in the netmask.
    */
  public String ipAddrString() {
    return (IP_s.IPtoString(ipAddr)+"/"+maskBits);
  }

  /** True if this interface is a virtual interface (handles no 
   *  packets, has no link layer, serves only to provide an address
   *  to loop back to).
   */
  public boolean isVirtual;

  /** TCPDUMP probe to which traffic is dumped (if non-null). */
  tcpDump dumpTo;

  static final boolean BINARY_TCPDUMP = true;

  /** Instance of packetQueue for outgoing traffic. */
  public packetQueue queueManager;

  /** Instance of PacketQueueMonitor monitoring the queueManager. */
  public PacketQueueMonitor queueMonitor = null;

  /** Reference to the ProtocolSession configured over this NIC (generally, 
    * but not necessarily, the local IP session).
    */
  protected ProtocolSession attachedProtocol;

  /** Bring the NIC up at runtime after it was brought down via NIC.down(),
   *  and inject an appropriate route into the FIB.  
   */
  public void up() {
	ifaceUp = true;

	try {
	    Host H = (Host)inGraph();
	    IP IPsess = (IP)H.SessionForName("ip");
	    RoutingTable RT = IPsess.getRoutingTable();
	
	    linkLayer LL = link_hw;
	    if (LL == null) {
		if (!isVirtual) {
		    System.err.println("Warning: interface "+ID+" of "+H+
				       " has not been configured");
		}
	    } else {
		link L = LL.onLink();
		if (L!=null) RT.add(L.ip,this,0,1,"iface");
	    }
	} catch (ProtocolException pex) {
	    System.err.println("NIC.up(): Host "+inGraph()+
			       " doesn't implement IP?");
	}
  }

  /** Force the NIC to go down at runtime - it will drop all packets sent 
   *  and received. All NIC state variables remain unchanged.  Update the 
   *  FIB to reflect the loss of the route.
   */
  public void down() {
	ifaceUp = false;

	try {
	    Host H = (Host)inGraph();
	    IP IPsess = (IP)H.SessionForName("ip");
	    RoutingTable RT = IPsess.getRoutingTable();
	    
	    linkLayer LL = link_hw;
	    if (LL != null) {
		link L = LL.onLink();
		if (L!=null) RT.del(L.ip,"iface");
	    }
	} catch (ProtocolException pex) {
	    System.err.println("NIC.down(): Host "+inGraph()+
			       " doesn't implement IP?");
	}
  }

  /** Return the value of ifaceUp, the flag indicating that the interface
   *  is ready to send and receive IP packets.
   */
  public boolean isUp() {
    return ifaceUp;
  }

  /** Flag to indicate that the interface is configured, has been 
    * opened, and is ready for service. 
    */
  protected boolean ifaceUp;

  /** In-channel on which packets are received. */ 
  public inChannel IN;

  /** Out-channel to which packets are written. */ 
  public outChannel OUT;

  /** Link-layer interface that handles packet delivery */
  public linkLayer link_hw;

  private boolean DEBUG = false;

//------------------------------------------------------ PROCESSES

  /** SSF process that receives incoming packets, and pushes their 
    * contents to the protocol that opened the NIC.   This is the NIC's
    * only process.   Except for callbacks that take place as a result 
    * of timer expiration, all packet-handling activity within a 
    * ProtocolGraph takes place as a result of, and on the execution 
    * stack of, the receiver process of one of its configured NICs. 
    */
  public process receiver; 

  /** Create the receiver process that reads packets from the IN channel. 
    * This process reads all received packets, logs them using tcpDump
    * if logging is enabled, and pushes them to the protocol session 
    * configured above the NIC.  When packet processing is complete, 
    * the NIC goes back to sleep and waits for more packets to arrive.
   */
  private void makeReceiver() {
    receiver = new process(attachedProtocol.inGraph()) {
      public boolean isSimple() {
	return true;
      }
      public void action() {
	try {
	  //System.err.println("Receiver of "+inGraph()+" at "+owner().now());
	  /*SSF.*/Event[] evts = link_hw.receive(NIC.this); 
	  for (int e=0; e<evts.length; e++) {
	    PacketEvent pevt = (PacketEvent)evts[e];

	    if (!ifaceUp) drop(pevt.getHeaders());
                
	    else {
	      if (dumpTo!=null) dumpTo.dump(pevt);
	      attachedProtocol.push(pevt.getHeaders(),NIC.this);
	    }
	  }
	} catch (ProtocolException pe) {
	  System.err.println(pe);
	  
	  // STUB -- should do better error handling
	  
	}
	waitOn(IN);
      }
    };
  }
    
//--------------------------------------------------------- CLOCK FREQUENCY

  /** Clock frequency.  Defines the minimum logical time resolution of the 
    * interface.   Normally initialized with reference to the global Net.
    */ 
  protected long FREQUENCY;

  /** Interface clock frequency, in "ticks per second" (Hertz) **/  
  public final long frequency() {
    return FREQUENCY;
  }

//--------------------------------------------------------- FLAKINESS

  /** Flake probability.  Transmitted packets are dropped with the 
    * given probability.  Default is zero (100% reliable).
    */
  protected double FLAKINESS;

 /** Default flakiness: 0.0 */
  public final double DEFAULT_FLAKINESS  = 0.0;

 /** Flaky drop random generator */
  protected RandomDistribution flakyGen = null;

  /** If true, a flaky NIC will avoid dropping TCP empty ACK packets,
   *  to avoid dropping SYN ACKs and FIN ACKs. The intention is to drop 
   *  only TCP data segment packets.
   */
  protected boolean CHECK_FOR_SYNS = false;

  /** If true, a flaky NIC count the drops due to flakiness, which will (can) 
   *  then be collected by the queue monitor and logged. In addition to (or 
   *  instead of) buffer drops.
   */
  protected boolean COUNT_FLAKY_DROPS = false;

  /**
   * Number of flaky drops.
   */
  protected long    flakyDrops = 0;


 /** Test whether should drop packet due to unreliable link. */
  public final boolean drop_flaky(ProtocolMessage msg) { 
      if (CHECK_FOR_SYNS) {
	IpHeader iph = (IpHeader) msg;
	
	if (iph.PROTOCOL_NO == Protocols.getProtocolByName("tcp")) {
	    TCP_Message tcp_msg = (TCP_Message)(msg.payload());
	    
	    if((tcp_msg.flags & TCP_Header.SYN) == TCP_Header.SYN) {
		if (DEBUG)
		    System.err.println("found SYN, len " 
				       + iph.bytecount() + " B");
	    }
	    else
		if (FLAKINESS>0.)
		    return(flakyGen.nextDouble() < FLAKINESS);
	}
      } else if (FLAKINESS>0.)
	  return(flakyGen.nextDouble() < FLAKINESS);

    return false;
 };

//--------------------------------------------------------- BIT RATE

  /** Interface hardware bit rate.  Defines the number of bits per second
    * that the interface can write onto link during a sustained burst.
    */
  protected double BIT_RATE;	

  /** Default bitrate: 10Mbit/s */
  public final double DEFAULT_BIT_RATE = 10.*1024*1024; 

 /** Max bits emitted per simulator tick (i.e., frequency-scaled) */
  public final double bitrate() { 
    return (BIT_RATE/frequency()); 
  };

//--------------------------------------------------------- INTERNAL LATENCY

  /** Interface internal latency.  Defines the number of seconds 
    * that elapse between the time a message is enqueued and the 
    * time it is written out.
    */
  protected double DELAY;    

  /** Default internal interface latency: 100 microseconds 
   */
  public final double DEFAULT_DELAY = 0.000100;

 /** Internal delay time of network interface, in ticks (frequency-scaled) 
  */
  public final long delay() { return (long)(DELAY*frequency());};

//--------------------------------------------------------- BUFFER SIZE

  /** Buffer size.  Defines the number of bytes in the transmit buffer 
    * of this NIC.  Defaults to infinite-buffer.
    */
  protected long buffersize;
  public final long DEFAULT_BUFFERSIZE = Long.MAX_VALUE;


//---------------------------------------------------------- CONSTRUCTOR

  /** Constructor for a network interface card.  The card is 'down'
   *  until it is open()ed during link configuration.<p>
   @arg G protocol graph (Host) within which the NIC is configured.
   @arg use_id integer ID identifying this NIC among all the Host's NICs
   */
  public NIC(ProtocolGraph G, int use_id) throws ProtocolException {
    setGraph(G);
    IN = new inChannel(G);
    ID = use_id;

    ipAddr       = -1;
    maskBits     = 32;
    ifaceUp      = false;
    buffersize   = DEFAULT_BUFFERSIZE;
    FREQUENCY    = Net.frequency;
    BIT_RATE     = DEFAULT_BIT_RATE;
    DELAY        = DEFAULT_DELAY;
    FLAKINESS    = DEFAULT_FLAKINESS;
  }

  // changed by ato 9/21/00
  /** Returns a string tersely describing the interface configuration. */
  public String toString() {
    return ("[NIC "+interfaceIdentifier()+
	    ": bitrate="+BIT_RATE+", latency="+DELAY+
	    ", buffer= "+buffersize+"]");
	    
  }

  /** Returns a terse interface identification in format "NHI_address IP_address". */
  public String interfaceIdentifier() {
    return (((Host)inGraph()).nhi+"("+ID+") "+IP_s.IPtoString(ipAddr)+"/"+maskBits+" :");
  }

//---------------------------------------------------------- CONFIGURATION

 /**
   * Configure a network interface card. The supported DML attributes are:
   *  <PRE>
   *  interface [
   *    id       %I               # Mandatory identification number unique in host, int
   *    idrange [from %I to %I]   # alternative array notation for identification numbers
   *
   *    ip       %S               # Optional IP address (String "a.b.c.d/m")
   *
   *    bitrate  %F               # Output bits per second  (float),
   *                              # if omitted default bitrate = 10.*1024*1024 = 10Mbs
   *    latency  %F               # Internal delay in seconds (float),
   *                              # if omitted default latency = 0.0001 sec.
   *    virtual  %S               # If "true" construct a virtual interface, true/false,
   *                              # if omitted virtual is "false".
   *    flaky [                   # Force packet drops with given probability
   *       threshold %F           # 0 <= probability <= 1
   *       distribution [         # see documentation on configuration of 
   *        ...                   # random generators.
   *       ]                      # 
   *       check_for_syns         # if true, don't drop SYN packets.
   *       count_flaky_drops      # if true, keep a counter of the number of 
   *                              # packets dropped due to flakiness.
   *                              #
   *    ]                         # if "flaky" is omitted, probability = 0.
   *                              # 
   *    tcpdump  %S               # Optional tcpdump filename, uses SSF.OS.binaryTcpDump
   *
   *    queue [ use %S ]          # Use class named in String %S for SSF.Net.packetQueue,
   *                              # if omitted, the default queue class is SSF.Net.droptailQueue.
   *    buffer   %I               # Packet queue buffer size in bytes (int) used by packetQueue,
   *                              # if omitted, default queue size is "infinite".
   *
   *    monitor [ use %S ]        # Use class named in String %S for the queue monitor, optional.
   *  ]
   * </PRE>
   *  If the time to send one bit is less than the resolution of the 
   *  simulation clock (specified by Net.frequency), clock underflow 
   *  would result, preventing precise determination of the times 
   *  at which packets should be written, and causing multiple packets 
   *  to be sent on the wire at "the same" time.   This condition 
   *  therefore throws a configException; to resolve it, reduce the 
   *  NIC bitrate or increase Net.frequency.
   *
   */
  public void config(Configuration cfg) throws configException {

    super.config(cfg);
    String str;

    str = (String)cfg.findSingle("ip");      	
    if (str!=null) {
      ipAddr = IP_s.StrToInt(str);
      maskBits = IP_s.getMaskBits(str);
    }

    str = (String)cfg.findSingle("debug");
    if (str != null)
      DEBUG = Boolean.valueOf(str).booleanValue();

    str = (String)cfg.findSingle("bitrate");
    if (str != null)
      BIT_RATE = (new Double(str)).doubleValue();

    //if (bitrate() > Net.frequency) bugfix by ato 9/21/00. Will flag bad old models.
    if (BIT_RATE > Net.frequency)
      throw new configException
	("\nNIC "+interfaceIdentifier()+"  bitrate ("+BIT_RATE+" bps) faster than "+
	 "simulated time resolution ("+1.0/Net.frequency+" sec)\n ---> increase Net.frequency!");

    str = (String)cfg.findSingle("latency");
    if (str != null)
      DELAY = (new Double(str)).doubleValue();
	
    str = (String)cfg.findSingle("buffer");
    if (str != null)
      buffersize = new Long(str).longValue();

    // tcpdump output file. Not treated with SSF.Util.Streams.
    str = (String)cfg.findSingle("tcpdump");    	
    if (str!=null) try {
      if (BINARY_TCPDUMP)
	dumpTo = new binaryTcpDump(new FileOutputStream(str),inGraph());
      else
	dumpTo = new tcpDump(new PrintWriter(new FileOutputStream(str)),inGraph());			
    } catch (IOException ioex) {
      ioex.printStackTrace(); // oh well
    }

    str = (String)cfg.findSingle("virtual");
    isVirtual = (str != null && "true".equals(str));

    // This attribute should be eventually obsoleted or else
    // get improved with a reproducible, configurable loss generator class.
    Configuration fcfg = (Configuration) cfg.findSingle("flaky");
    if (fcfg!=null) {
      str = (String) fcfg.findSingle("threshold");
      if (str!=null)
	FLAKINESS = (new Double(str)).doubleValue();
      else
	throw new configException("flaky.threshold not found");

      str = (String)fcfg.findSingle("check_for_syns");
      if (str != null) {
	  CHECK_FOR_SYNS = Boolean.valueOf(str).booleanValue();
	  //	  System.err.println("check_for_syns: " + CHECK_FOR_SYNS);

      }
      str = (String)fcfg.findSingle("count_flaky_drops");
      if (str != null) {
	  COUNT_FLAKY_DROPS = Boolean.valueOf(str).booleanValue();
	  //	  System.err.println("count_flaky_drops: " + COUNT_FLAKY_DROPS);
      }

      Configuration dcfg = (Configuration)fcfg.findSingle("distribution");
      if(dcfg == null)
	throw new configException("flaky.distribution not found");

      flakyGen = 
	Net.accessRandomDistribution(this, dcfg, "flaky_nic"); 
    }

    // packetQueue class selection and its configuration.
    // This must follow reading all other interface attributes.
    // completely redesigned 9/24/00 by ato.
    // Will break old models that used string-valued "queue" attribute.
    Configuration qcfg = (Configuration)cfg.findSingle("queue");
    if (qcfg != null)
      createQueue(qcfg);
    else     // default to SSFNet release 1.1 or earlier
      queueManager = new droptailQueue(this);

    // QueueMonitor class selection and its configuration.
    // added 9/24/00 by ato.
    Configuration mConfig = (Configuration)cfg.findSingle("monitor");
    if (mConfig != null) createMonitor(mConfig);

    // This attribute is really obsoleted
    str = (String)(cfg.findSingle("ptp"));
    if (str!=null)
      System.err.println
	(interfaceIdentifier()+" ** Warning: interface configuration "+
	 "contains obsolete ptp attribute; IGNORING");

    // This attribute is really obsoleted
    String linkdelay = (String)cfg.findSingle("linkdelay");
    if (linkdelay != null)
      System.err.println
	(interfaceIdentifier()+" ** Warning: interface configuration "+
	 "contains obsolete linkdelay attribute; IGNORING");
  }

  /** Instantiate and configure an implementation of packetQueue */
  private void createQueue(Configuration config) throws configException {

    String str = (String)config.findSingle("use");	
    if (str == null)
      throw new configException("\n"+interfaceIdentifier()+" DML queue.use is not specified");

    try {
      Class mClass = Class.forName(str);
      Object mobj = mClass.newInstance();
      queueManager = (SSF.Net.packetQueue)mobj;
    }
    catch (Exception any) {
      System.err.println(interfaceIdentifier()+" Can't create an instance of "+str);
      any.printStackTrace();
      throw new configException(interfaceIdentifier()+" Can't create queue "+str);
    }

    //let the queue do its own config, and let it know the owner NIC
    queueManager.config(this, config);
  }

  /** Instantiate and configure a queue monitor */
  private void createMonitor(Configuration config) throws configException {
    String monitor_type = (String)config.findSingle("use");
	
    if (monitor_type == null)
      throw new configException("\n"+interfaceIdentifier()+" DML monitor.use is not specified");

    try {
      Class mClass = Class.forName(monitor_type);
      Object mobj = mClass.newInstance();
      queueMonitor = (PacketQueueMonitor)mobj;
    }
    catch (Exception any) {
      System.err.println(interfaceIdentifier()+" Can't create an instance of "+monitor_type);
      any.printStackTrace();
      throw new configException("Can't create monitor "+monitor_type);
    }

    //let the monitor do its own config, and let it know the owner NIC
    queueMonitor.config(this, config);
  }

  /** This method calls init() in the installed packetQueue and packetQueueMonitor
      implementations.
   */
  public void init() throws ProtocolException {
    queueManager.init();
    if (queueMonitor != null) queueMonitor.init();
  }

//----------------------------------------------- OPEN, CLOSE, PUSH

  /** Bring up this NIC.  The "request" argument names the link layer
    * interface that will be handling actual packet delivery. 
    * To bring up a NIC, its OUT channel is mapped to the IN() channel 
    * of the peer (the link hardware takes care of this), and the 
    * send/receive processes are initialized. <p> 
    * This method is called by a <b>link</b> object when a link is 
    * being brought up.  The link can be thought of as the "introducer"
    * that brings NIC and IP together for each interface making up the link.
    * In response to the open() request, the NIC superclass calls opened() 
    * on the higher-level protocol, thus completing the handshake.
    */
  public void open(ProtocolSession higher, Object request) 
    throws ProtocolException {

      if (isVirtual) {
	System.err.println("** NIC "+IP_s.IPtoString(ipAddr)+
			   " (in host "+((Host)inGraph()).nhi+
			   ") may not be attached in link; is virtual");
	System.exit(-1);
      }

      if (link_hw!=null) {
	System.err.println("** NIC "+IP_s.IPtoString(ipAddr)+
			   " (in host "+((Host)inGraph()).nhi+
			   ") already attached; cannot reattach as "+
			   request);

	// could throw ProtocolException but this is really fatal. --jhc
	System.exit(-1);
      } 

      link_hw = (linkLayer)request;

      if (!ifaceUp) {

	attachedProtocol = higher;

	up();	

	link_hw.up(this);
	makeReceiver();
      }

      super.open(higher,request);  // superclass bookkeeping
      if(DEBUG) System.out.println("NIC.open: "+toString());
    }


  /** Bring down this NIC.  Messages that arrive and any messages 
    * pending delivery will be dropped. 
    */
  public void close(ProtocolSession higher) throws ProtocolException {
    ipAddr = -1;
    down();
    super.close(higher); // superclass bookkeeping
  }

  /** Process a ProtocolMessage pushed down from above, by handing 
    * it off to the queueManager to be written out to the network.
    */
  public boolean push(ProtocolMessage message, ProtocolSession fromSession) {

    if (!ifaceUp || drop_flaky(message)) {

      drop(message);

      if (COUNT_FLAKY_DROPS)
	  flakyDrops++;

      return false;

    } else return queueManager.enqueue(message);  

  }

  /** Drop a transmitted or received protocol message (because the NIC 
    * is down, or because queue overflows, or because the interface is flaky).
    */
  public void drop(ProtocolMessage dropped) {
    if(DEBUG)
      System.err.println(interfaceIdentifier() +" dropping IP packet "+ dropped);
    if (dumpTo!=null) dumpTo.drop(new PacketEvent(dropped));
  }

}



/*=                                                                      =*/
/*=  Copyright (c) 1997--2000  SSF Research Network                      =*/
/*=                                                                      =*/
/*=  SSFNet is open source software, distributed under the GNU General   =*/
/*=  Public License.  See the file COPYING in the 'doc' subdirectory of  =*/
/*=  the SSFNet distribution, or http://www.fsf.org/copyleft/gpl.html    =*/
/*=                                                                      =*/
